﻿/**
* CRL
*/
using CRL.Core.Extension;
using CRL.Core.Remoting;
using CRL.Core.Request;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Net;
using System.Net.Http;
//using System.Net.Http;
//using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace CRL.Core.ApiProxy
{
    class ApiClient : AbsClient
    {
        internal Dictionary<string, object> requestHeads;
        HttpClient _httpClient;
        public ApiClient(AbsClientConnect _clientConnect, HttpClient httpClient = null) : base(_clientConnect)
        {
            _httpClient = httpClient;
        }
        static Dictionary<ContentType, string> ContentTypeDic = new Dictionary<ContentType, string>() { { ContentType.JSON, "application/json" },
            { ContentType.XML, "application/xml" },
            { ContentType.FORM, "application/x-www-form-urlencoded" },
            { ContentType.NONE, "text/plain" },
        };
        object SendRequest(serviceInfo serviceInfo, methodInfo methodInfo, object[] args)
        {
            //var method = serviceInfo.GetMethod(methodName);
            var serviceAttribute = serviceInfo.GetAttribute<ServiceAttribute>();
            var methodAttribute = methodInfo.GetAttribute<MethodAttribute>();
            var argsName = methodInfo.MethodInfo.GetParameters();
            var returnType = methodInfo.MethodInfo.ReturnType;
            var contentType = ContentType.JSON;
            var serviceName = serviceInfo.ServiceType.Name;
            var hostAddress = HostAddress;
            if (serviceAttribute != null)
            {
                if (serviceAttribute.ContentType != ContentType.NONE)
                {
                    contentType = serviceAttribute.ContentType;
                }
                if (!string.IsNullOrEmpty(serviceAttribute.Name))
                {
                    serviceName = serviceAttribute.Name;
                }
                if (!string.IsNullOrEmpty(serviceAttribute.GatewayPrefix))
                {
                    hostAddress.serviceNamePrefix = serviceAttribute.GatewayPrefix;
                }
                if (!string.IsNullOrEmpty(serviceAttribute.HostUrl))
                {
                    hostAddress.address = serviceAttribute.HostUrl;
                }
            }
            var apiClientConnect = clientConnect as ApiClientConnect;
            var httpMethod = HttpMethod.POST;
            var responseContentType = contentType;
            var requestPath = $"/{apiClientConnect.Apiprefix}/{serviceName}/{methodInfo.MethodInfo.Name}";
            if (methodAttribute != null)
            {
                httpMethod = methodAttribute.Method;
                if (!string.IsNullOrEmpty(methodAttribute.Path))
                {
                    requestPath = methodAttribute.Path;
                    if (!requestPath.StartsWith("/"))
                    {
                        requestPath = "/" + requestPath;
                    }
                }
                if (methodAttribute.ContentType != ContentType.NONE)
                {
                    contentType = methodAttribute.ContentType;
                    responseContentType = contentType;
                }
                if (methodAttribute.ResponseContentType != ContentType.NONE)
                {
                    responseContentType = methodAttribute.ResponseContentType;
                }
            }

            var url = hostAddress.GetHttpAddress() + requestPath;
            var request = new ImitateWebRequest(ServiceName, apiClientConnect.Encoding, _httpClient);
            request.ContentType = ContentTypeDic[contentType];
            var parameList = new List<parameItem>();
            for (int i = 0; i < argsName.Length; i++)
            {
                var p = argsName[i];
                var value = args[i];
                parameList.Add(new parameItem { name = p.Name, value = value, isGet = p.GetCustomAttribute<MethodGetAttribute>() != null });
            }
            string getParame(IEnumerable<parameItem> parame)
            {
                var list = new List<string>();
                foreach (var p in parame)
                {
                    list.Add(string.Format("{0}={1}", p.name, p.value));
                }
                var str = string.Join("&", list);
                return str;
            }

            if (requestHeads != null)
            {
                foreach (var kv in requestHeads)
                {
                    request.SetHead(kv.Key, kv.Value);
                }
            }
            string postArgs = "";
            if (httpMethod != HttpMethod.GET)
            {
                var argsPost = parameList.Where(b => !b.isGet);
                if (argsPost.Any())
                {
                    switch(contentType)
                    {
                        case ContentType.JSON:
                            postArgs = argsPost.First().value?.ToJson();
                            break;
                        case ContentType.XML:
                            postArgs = SerializeHelper.XmlSerialize(argsPost.First().value, apiClientConnect.Encoding);
                            break;
                        case ContentType.FORM:
                            if (argsPost.Count() > 1)//区分对象还是多个参数
                            {
                                postArgs = FormatFormData(argsPost.ToDictionary(b => b.name, b => b.value));
                            }
                            else
                            {
                                postArgs = FormatFormData(argsPost.First().value);
                            }
                            break;
                        default:
                            postArgs = argsPost.First().value?.ToString();
                            break;
                    }
                }
                var getMembers = parameList.Where(b => b.isGet);
                if (getMembers.Any())
                {
                    url = $"{url}?{getParame(getMembers)}";
                }
            }
            else
            {
                url = $"{url}?{getParame(parameList)}";
                //result = request.Get($"{url}?{str}");
            }
            bool isTask = methodInfo.IsAsync;
            var generType = returnType;
            if (isTask)
            {
                generType = returnType.GenericTypeArguments[0];
            }
            var pollyAttr = serviceInfo.GetAttribute<PollyAttribute>();
            Func<string, object> dataCall = (msg) =>
             {
                 apiClientConnect.OnAfterRequest?.Invoke(url, msg);
                 object returnObj = msg;
                 try
                 {
                     if (generType != typeof(string))
                     {
                         if (responseContentType == ContentType.JSON)
                         {
                             returnObj = SerializeHelper.DeserializeFromJson(msg, generType);
                         }
                         else if (responseContentType == ContentType.XML)
                         {
                             returnObj = SerializeHelper.XmlDeserialize(generType, msg, apiClientConnect.Encoding);
                         }
                     }
                 }
                 catch (Exception ero)
                 {
                     var eroMsg = $"反序列化为{generType.Name}时出错:" + ero.Message;
                     Core.EventLog.Error(eroMsg + " " + msg);
                     throw new Exception(eroMsg);
                 }
                 //转换为实际的数据类型
                 return returnObj;
             };
            if (System.Diagnostics.Debugger.IsAttached)
            {
                Console.WriteLine($"{httpMethod} {contentType} {url}");
                Console.WriteLine($"body: {postArgs}");
            }
            if (methodInfo.IsAsync)
            {
                var asynResult = SendRequestAsync(pollyAttr, request, url, httpMethod.ToString(), postArgs, $"{ServiceName}.{methodInfo.MethodInfo.Name}", dataCall);
                var task = methodInfo.TaskCreater();
                task.ResultCreater = async () =>
                {
                    return await asynResult;
                };
                return task.InvokeAsync();
            }
            return SendRequest(pollyAttr, request, url, httpMethod.ToString(), postArgs, $"{ServiceName}.{methodInfo.MethodInfo.Name}", dataCall);
        }
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            return TryInvokeMember(binder.Name, args, out result);
        }
        public override bool TryInvokeMember(string methodName, object[] args, out object result)
        {
            var method = serviceInfo.GetMethod(methodName);
            if (method == null)
            {
                throw new Exception($"method not found {methodName}");
            }
            var returnType = method.MethodInfo.ReturnType;
            var response = SendRequest(serviceInfo, method, args);
            if (returnType == typeof(void))
            {
                result = null;
                return true;
            }
            result = response;

            return true;
        }
        static string FormatFormData(object obj)
        {
            //like Args[MapSpid]=1&Args[StartTime]1=&Args[EndTime]=&Args[Status]=&_search=false&nd=1573883648354&rows=100&page=1&sidx=&sord=asc
            var type = obj.GetType();
            var formFields = new List<string>();
            splitFromObj(obj, type, "", formFields);
            return string.Join("&", formFields);
        }
        static string urlEncode(object value)
        {
#if NETSTANDARD
            System.Web.HttpUtility.UrlEncode(value?.ToString());
#endif
            return value?.ToString();
        }
        static void splitFromObj(object obj, Type type, string parentName, List<string> list)
        {
            var pros = type.GetProperties();
            if(obj is IDictionary)
            {
                var list2 = (IDictionary)obj;
                var enumerator = list2.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    var key = enumerator.Key;
                    var value = enumerator.Value;
                    list.Add($"{key}={urlEncode(value)}");
                }
                return;
            }
            if (type == typeof(string))
            {
                list.Add($"{parentName}={urlEncode(obj)}");
                return;
            }
            foreach (var p in pros)
            {
                var value = p.GetValue(obj);
                var memberName = string.IsNullOrEmpty(parentName) ? p.Name : $"[{p.Name}]";
                if (p.PropertyType.IsClass && p.PropertyType != typeof(string) && !typeof(IEnumerable).IsAssignableFrom(p.PropertyType))//instance
                {
                    splitFromObj(value, p.PropertyType, $"{parentName}{memberName}", list);
                }
                else if (typeof(IDictionary).IsAssignableFrom(p.PropertyType))
                {
                    splitFromObj(value, p.PropertyType, $"{parentName}{memberName}", list);
                }
                else if (typeof(IEnumerable).IsAssignableFrom(p.PropertyType) && p.PropertyType != typeof(string))//IEnumerable
                {
                    var undType = p.PropertyType.GenericTypeArguments[0];
                    splitFromObj(value, undType, $"{parentName}{memberName}[0]", list);
                }
                else//field
                {
                    list.Add($"{parentName}{memberName}={urlEncode(value)}");
                }
            }
        }
    }
    class parameItem
    {
        public string name;
        public object value;
        public bool isGet;
    }
}
